package com.ylr.gateway.framework.filter;

import com.alibaba.fastjson2.JSON;
import com.ylr.base.common.constant.Constant;
import com.ylr.base.common.constant.RedisConstant;
import com.ylr.base.common.enums.ChannelEnum;
import com.ylr.base.common.enums.ModuleEnum;
import com.ylr.base.common.enums.ResultEnum;
import com.ylr.base.common.interfaces.BaseResultEnum;
import com.ylr.base.common.po.user.LoginUser;
import com.ylr.base.common.po.user.UserAuthenticationDto;
import com.ylr.base.common.po.user.UserPo;
import com.ylr.base.common.tool.Result;
import com.ylr.jwt.common.enums.JwtResultEnum;
import com.ylr.jwt.framework.service.JwtService;
import com.ylr.redis.framework.service.RedisHashService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;


/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2023-04-15 11:15:27
 * className: AuthGlobalFilter 全局权限校过滤器
 * version: 1.0
 * description:
 *  `@AllArgsConstructor`：构造器方式注入bean
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(AuthGlobalFilter.class);

    @Value("${spring.application.simple-name}")
    private String applicationSimpleName;

    private final JwtService jwtService;

    private final RedisHashService redisHashService;

    public AuthGlobalFilter(JwtService jwtService, RedisHashService redisHashService) {
        this.jwtService = jwtService;
        this.redisHashService = redisHashService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        /*
            用户权限校验
         */
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders requestHeaders = request.getHeaders();

        /*
            判断请求是否通过白名单
         */
        String authResult = requestHeaders.getFirst(Constant.AUTH_RESULT);
        if (Constant.AUTH_PASS.equals(authResult)) {
            // 已通过白名单
            return chain.filter(exchange);
        }

        // 获取用户令牌
        String token = requestHeaders.getFirst(Constant.AUTHORIZATION);
        if (StringUtils.isEmpty(token)) {
            log.warn("请求没有携带令牌数据");
            return writeResponse(exchange, ResultEnum.TOKEN_LOSE);
        }
        String[] arr = token.split(StringUtils.SPACE);
        if (arr.length != 2) {
            log.warn("令牌格式错误。token={}", token);
            return writeResponse(exchange, ResultEnum.TOKEN_FAIL);
        }
        // 令牌所属渠道
        String channelCode = arr[0];
        // 用户令牌
        token = arr[1];

        ChannelEnum channelEnum = ChannelEnum.getChannelEnum(channelCode);
        if (Objects.isNull(channelEnum)) {
            log.warn("令牌对应的渠道不存在。channelCode={}", channelCode);
            return writeResponse(exchange, ResultEnum.CHANNEL_FAIL);
        }

        String path = request.getURI().getPath();
        log.debug("path={}", path);
        if (StringUtils.isEmpty(path)) {
            log.debug("AuthGlobalFilter 请求地址为空");
            return chain.filter(exchange);
        }
        StringBuilder sb = new StringBuilder(path);
        int index = sb.indexOf("/", 1);
        // 获取请求的模块code
        String moduleCode = sb.substring(1, index);
        log.debug("moduleCode={}", moduleCode);
        // 获取模块枚举对象
        ModuleEnum moduleEnum = ModuleEnum.getModuleEnum(moduleCode);
        if (Objects.isNull(moduleEnum)) {
            /*
                请求模块不存在
                出现这个问题的可能原因：
                1、路由配置了可行的规则，但是根据‘请求地址’无法匹配到对应的模块
             */
            log.warn("请求接口对应的模块不存在。moduleCode={}", moduleCode);
            return writeResponse(exchange, ResultEnum.MODULE_FAIL);
        }

        /*
            根据 模块枚举对象 和 请求地址 进行权限校验
         */
        String userId = checkToken(channelEnum, token);
        if (StringUtils.isBlank(userId)) {
            log.warn("令牌携带的用户主键为空。token={}", token);
            return writeResponse(exchange, JwtResultEnum.LOSE.getCode(), JwtResultEnum.LOSE.getMsg());
        }
        log.debug("userId={}", userId);

        authResult = StringUtils.SPACE;
        String userPo = StringUtils.EMPTY;

        // 从缓存中获取登录用户数据
        LoginUser loginUser = redisHashService.get(RedisConstant.SECURITY_USER_MAP, channelCode + Constant.SPLIT + userId);
        if (Objects.nonNull(loginUser)) {
            /*
                权限校验
             */

            // 获取请求接口地址
            String _path = sb.substring(index);
            log.debug("_path={}", _path);

            UserAuthenticationDto authenticationDto = loginUser.getAuthenticationDto();

            Map<Integer, Set<String>> moduleUriSetMap = authenticationDto.getModuleUriSetMap();
            Set<String> uriSet = moduleUriSetMap.getOrDefault(moduleEnum.getValue(), Collections.emptySet());

            boolean bool = uriSet.contains(_path);
            if (!bool) {
                Map<Integer, List<String>> moduleUriListMap = authenticationDto.getModuleUriListMap();
                List<String> uriList = moduleUriListMap.getOrDefault(moduleEnum.getValue(), Collections.emptyList());

                PathMatcher pathMatcher = new AntPathMatcher();
                for (String uri : uriList) {
                    // 模糊匹配
                    if (pathMatcher.match(uri, _path)) {
                        bool = true;
                        break;
                    }
                }
            }
            if (!bool) {
                // 权限校验拒绝
                return writeResponse(exchange, ResultEnum.AUTH_FAIL);
            }
            authResult = Constant.AUTH_PASS;
            userPo = JSON.toJSONString(loginUser.getUserPo());
        }

        request = request
                .mutate()
                .header(Constant.SOURCE, applicationSimpleName)
                .header(Constant.MODULE, moduleCode)
                .header(Constant.CHANNEL, channelCode)
                .header(Constant.AUTH_RESULT, authResult)
                .header(Constant.USER_ID, userId)
                .header(UserPo.class.getSimpleName(), userPo)
                .build();
        exchange = exchange.mutate().request(request).build();
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 数值越小，优先级越高
        return 0;
    }

    /**
     * 用户短令牌校验
     * @param channelEnum 请求渠道枚举对象
     * @param token       用户短令牌
     * @return 用户主键
     */
    private String checkToken(ChannelEnum channelEnum, String token) {
        if (channelEnum == ChannelEnum.WEB) {
            // 校验web端令牌
            return jwtService.checkWebToken(token);
        }

        // 代码不会走到这里
        return null;
    }

    /**
     * 封装返回数据
     * @param exchange       服务交换机对象
     * @param baseResultEnum 请求结果枚举接口
     * @return 返回结果
     */
    private Mono<Void> writeResponse(ServerWebExchange exchange, BaseResultEnum baseResultEnum) {
        HttpHeaders requestHeaders = exchange.getRequest().getHeaders();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders responseHeaders = response.getHeaders();
        response.setStatusCode(HttpStatus.OK);
        responseHeaders.setContentType(MediaType.valueOf("application/json;charset=UTF-8"));
        responseHeaders.add("Access-Control-Allow-Credentials", "true");
        responseHeaders.add("Access-Control-Allow-Origin", requestHeaders.getFirst("Origin"));
        Result<Object> result = Result.getResult(baseResultEnum);
        DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONBytes(result));
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 封装返回数据
     * @param exchange 服务交换机对象
     * @param code     状态码
     * @param msg      返回消息
     * @return 返回结果
     */
    private Mono<Void> writeResponse(ServerWebExchange exchange, Integer code, String msg) {
        HttpHeaders requestHeaders = exchange.getRequest().getHeaders();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders responseHeaders = response.getHeaders();
        response.setStatusCode(HttpStatus.OK);
        responseHeaders.setContentType(MediaType.valueOf("application/json;charset=UTF-8"));
        responseHeaders.add("Access-Control-Allow-Credentials", "true");
        responseHeaders.add("Access-Control-Allow-Origin", requestHeaders.getFirst("Origin"));
        Result<Object> result = Result.getResult(code, msg);
        DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONBytes(result));
        return response.writeWith(Mono.just(buffer));
    }

}
